using Assets.Scripts;
using Assets.Scripts.Networking;
using Assets.Scripts.Objects;
using Assets.Scripts.Objects.Entities;
using Assets.Scripts.Util;
using BepInEx.Configuration;
using HarmonyLib;
using System;
using System.Reflection;
using Util.Commands;
using VisualmodBep;
using VisualmodBep.Helper;

namespace StationeersServerPreference {

    public class SspDifficulty {
        public delegate void PrintString(string str);

        public static void Print(DifficultySetting difficulty, PrintString print = null) {
            if (print == null) {
                print = Log.LogInfo;
            }
            if (difficulty.Hidden) {
                print($"Difficulty {difficulty.Id} (Normal) Hidden");
            } else {
                print($"Difficulty {difficulty.Id} (Normal)");
            }
            print($"  Creative                {difficulty.Creative.Value} (False)"); // BoolReference -> bool
            print($"  EatWhileHelmetClosed    {difficulty.EatWhileHelmetClosed.Value} (False)");
            print($"  DrinkWhileHelmetClosed  {difficulty.DrinkWhileHelmetClosed.Value} (False)");
            print($"  BreathingRate           {difficulty.BreathingRate.Value} (2.0)");
            print($"  FoodDecayRate           {difficulty.FoodDecayRate.Value} (1.0)");
            print($"  HungerRate              {difficulty.HungerRate.Value} (1.0)");
            print($"  HydrationRate           {difficulty.HydrationRate.Value} (1.0)");
            print($"  HygieneRate             {difficulty.HygieneRate.Value} (1.0)");
            print($"  JetpackRate             {difficulty.JetpackRate.Value} (1.0)");
            print($"  LungDamageRate          {difficulty.LungDamageRate.Value} (0.25)");
            print($"  MiningYield             {difficulty.MiningYield.Value} (1.0)");
            print($"  MoodRate                {difficulty.MoodRate.Value} (1.0)");
            print($"  OfflineMetabolism       {difficulty.OfflineMetabolism.Value} (0.1)");
            print($"  RobotBatteryRate        {difficulty.RobotBatteryRate.Value} (1.0)");
            print($"  WeatherLanderDamageRate {difficulty.WeatherLanderDamageRate.Value} (1.0)");
            if (difficulty.RespawnCondition != null) {
                print($"  RespawnCondition        {difficulty.RespawnCondition.Id} (Normal)");
            } else {
                print($"  RespawnCondition        null (Normal)");
            }
        }

        public static void PrintBrief(DifficultySetting difficulty, PrintString print = null) {
            if (print == null) {
                print = Log.LogInfo;
            }
            if (difficulty == null) {
                print("Difficulty null");
                return;
            }
            print($"Difficulty \"{difficulty.Id}\" Creative/Respawn (F/Normal) {printBool(difficulty.Creative)}/{difficulty.RespawnCondition?.Id}");
            print($"  Eat/DrinkWhileHelmetClosed          (F/F) {printBool(difficulty.EatWhileHelmetClosed)}/{printBool(difficulty.DrinkWhileHelmetClosed)}");
            print($"  Breathing/FoodDecay/JetpackRate (2/.25/1) {difficulty.BreathingRate.Value}/{difficulty.FoodDecayRate.Value}/{difficulty.JetpackRate.Value}");
            print($"  Hunger/Hydration/HygieneRate      (1/1/1) {difficulty.HungerRate.Value}/{difficulty.HydrationRate.Value}/{difficulty.HygieneRate.Value}");
            print($"  LungDamage/Mood/RobotBatteryRate  (1/1/1) {difficulty.LungDamageRate.Value}/{difficulty.MoodRate.Value}/{difficulty.RobotBatteryRate.Value}");
            print($"  Offline/MiningYield/LanderDamage (.1/1/1) {difficulty.OfflineMetabolism.Value}/{difficulty.MiningYield.Value}/{difficulty.WeatherLanderDamageRate.Value}");
        }

        private static char printBool(BoolReference boolReference) {
            return boolReference.Value ? 'T' : 'F';
        }

        public class Setter : ClassPropertyGetter {
            public Setter() : base(typeof(Setter)) {
            }

            private static Setter Instance = new Setter();

            public bool Creative {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.Creative, value)) {
                        Log.LogInfo($"SspDifficulty Setter Creative {value}");
                        SspConfig.Difficulty.Creative.Value = value;
                        DifficultySetting.Current.Creative.Value = value;
                        WorldManager.Instance.GameMode = value ? GameMode.Creative : GameMode.Survival; // DifficultySetting.SetCurrent()
                    }
                }
            }

            public bool EatWhileHelmetClosed {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.EatWhileHelmetClosed, value)) {
                        Log.LogInfo($"SspDifficulty Setter EatWhileHelmetClosed {value}");
                        SspConfig.Difficulty.EatWhileHelmetClosed.Value = value;
                        DifficultySetting.Current.EatWhileHelmetClosed.Value = value;
                    }
                }
            }

            public bool DrinkWhileHelmetClosed {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.DrinkWhileHelmetClosed, value)) {
                        Log.LogInfo($"SspDifficulty Setter DrinkWhileHelmetClosed {value}");
                        SspConfig.Difficulty.DrinkWhileHelmetClosed.Value = value;
                        DifficultySetting.Current.DrinkWhileHelmetClosed.Value = value;
                    }
                }
            }

            public float BreathingRate {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.BreathingRate, value)) {
                        Log.LogInfo($"SspDifficulty Setter BreathingRate {value}");
                        SspConfig.Difficulty.BreathingRate.Value = value;
                        DifficultySetting.Current.BreathingRate.Value = value;
                    }
                }
            }

            public float FoodDecayRate {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.FoodDecayRate, value)) {
                        Log.LogInfo($"SspDifficulty Setter FoodDecayRate {value}");
                        SspConfig.Difficulty.FoodDecayRate.Value = value;
                        DifficultySetting.Current.FoodDecayRate.Value = value;
                    }
                }
            }

            public float HungerRate {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.HungerRate, value)) {
                        Log.LogInfo($"SspDifficulty Setter HungerRate {value}");
                        SspConfig.Difficulty.HungerRate.Value = value;
                        DifficultySetting.Current.HungerRate.Value = value;
                    }
                }
            }

            public float HydrationRate {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.HydrationRate, value)) {
                        Log.LogInfo($"SspDifficulty Setter HydrationRate {value}");
                        SspConfig.Difficulty.HydrationRate.Value = value;
                        DifficultySetting.Current.HydrationRate.Value = value;
                    }
                }
            }

            public float HygieneRate {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.HygieneRate, value)) {
                        Log.LogInfo($"SspDifficulty Setter HygieneRate {value}");
                        SspConfig.Difficulty.HygieneRate.Value = value;
                        DifficultySetting.Current.HygieneRate.Value = value;
                    }
                }
            }

            public float JetpackRate {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.JetpackRate, value)) {
                        Log.LogInfo($"SspDifficulty Setter JetpackRate {value}");
                        SspConfig.Difficulty.JetpackRate.Value = value;
                        DifficultySetting.Current.JetpackRate.Value = value;
                    }
                }
            }

            public float LungDamageRate {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.LungDamageRate, value)) {
                        Log.LogInfo($"SspDifficulty Setter LungDamageRate {value}");
                        SspConfig.Difficulty.LungDamageRate.Value = value;
                        DifficultySetting.Current.LungDamageRate.Value = value;
                    }
                }
            }

            public float MiningYield {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.MiningYield, value)) {
                        Log.LogInfo($"SspDifficulty Setter MiningYield {value}");
                        SspConfig.Difficulty.MiningYield.Value = value;
                        DifficultySetting.Current.MiningYield.Value = value;
                    }
                }
            }

            public float MoodRate {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.MoodRate, value)) {
                        Log.LogInfo($"SspDifficulty Setter MoodRate {value}");
                        SspConfig.Difficulty.MoodRate.Value = value;
                        DifficultySetting.Current.MoodRate.Value = value;
                    }
                }
            }

            public float OfflineMetabolism {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.OfflineMetabolism, value)) {
                        Log.LogInfo($"SspDifficulty Setter OfflineMetabolism {value}");
                        SspConfig.Difficulty.OfflineMetabolism.Value = value;
                        DifficultySetting.Current.OfflineMetabolism.Value = value;
                    }
                }
            }

            public float RobotBatteryRate {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.RobotBatteryRate, value)) {
                        Log.LogInfo($"SspDifficulty Setter RobotBatteryRate {value}");
                        SspConfig.Difficulty.RobotBatteryRate.Value = value;
                        DifficultySetting.Current.RobotBatteryRate.Value = value;
                    }
                }
            }

            public float WeatherLanderDamageRate {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.WeatherLanderDamageRate, value)) {
                        Log.LogInfo($"SspDifficulty Setter WeatherLanderDamageRate {value}");
                        SspConfig.Difficulty.WeatherLanderDamageRate.Value = value;
                        DifficultySetting.Current.WeatherLanderDamageRate.Value = value;
                    }
                }
            }

            public string RespawnCondition {
                set {
                    if (ConfigValueChanged(SspConfig.Difficulty.RespawnCondition, value)) {
                        /*
                        foreach (WorldManager.ConditionData cond in WorldManager.RespawnConditions)
                        {
                            if (value.Equals(cond.Key, StringComparison.OrdinalIgnoreCase))
                            {
                                Log.LogInfo($"SspDifficulty Setter RespawnCondition \"{cond.Key}\"");
                                SetRespawnCondition(cond);
                                return;
                            }
                        }
                        Log.LogInfo($"SspDifficulty Setter RespawnCondition \"{value}\" not found");
                        */
                    }
                }
            }

            // ignore SspConfig.Difficulty.Enabled.Value
            public string Id {
                set {
                    foreach (DifficultySetting diff in DifficultySetting.AllSettings) {
                        if (value.Equals(diff.Id, StringComparison.OrdinalIgnoreCase)) {
                            Log.LogInfo($"SspDifficulty Setter Id \"{diff.Id}\"");
                            if (SspConfig.Difficulty.Enabled.Value) {
                                DifficultySetting.Current.Id = diff.Id;
                            } else {
                                DifficultySetting.SetCurrent(diff);
                            }
                            return;
                        }
                    }
                    Log.LogInfo($"SspDifficulty Setter Id \"{value}\" not found");
                }
            }

            public bool Enabled {
                set {
                    if (value != SspConfig.Difficulty.Enabled.Value) {
                        Log.LogInfo($"SspDifficulty Setter Enabled {value}");
                        SspConfig.Difficulty.Enabled.Value = value;
                    }
                }
            }

            public static void SetCommand(string propertyName, string value) {
                PropertyInfo prop = Instance.GetProperty(propertyName);
                if (prop == null) {
                    return;
                } else if (prop.PropertyType == typeof(float)) {
                    float v = float.Parse(value);
                    Log.LogInfo($"SetCommand float {propertyName}={v}");
                    prop.SetValue(Instance, v);
                } else if (prop.PropertyType == typeof(bool)) {
                    bool v = bool.Parse(value);
                    Log.LogInfo($"SetCommand bool {propertyName}={v}");
                    prop.SetValue(Instance, v);
                } else {
                    Log.LogInfo($"SetCommand {prop.PropertyType} {propertyName}={value}");
                    prop.SetValue(Instance, value); // fallback to string
                }
                // @TODO inform updating GUI
            }

            public bool ConfigValueChanged(ConfigEntry<bool> config, bool value) {
                return SspConfig.Difficulty.Enabled.Value && config.Value != value;
            }

            public bool ConfigValueChanged(ConfigEntry<float> config, float value) {
                return SspConfig.Difficulty.Enabled.Value && config.Value != value;
            }

            public bool ConfigValueChanged(ConfigEntry<string> config, string value) {
                return SspConfig.Difficulty.Enabled.Value && config.Value != value;
            }

            // @ref WorldSetting.OnRespawnPlayerKit(Human)
            private void SetRespawnCondition(StartConditionData cond) {
                string all_conds = "";
                foreach (StartConditionData c in StartConditionData.AllStartConditions) {
                    all_conds += c;
                    all_conds += ",";
                }
                string id = cond.Id;
                Log.LogInfo($"SetRespawnCondition {id} all possible values {all_conds}");
                SspConfig.Difficulty.RespawnCondition.Value = id;
                DifficultySetting.Current.RespawnCondition = id;
                // AccessTools.StaticFieldRefAccess<HashComparable<WorldManager.ConditionData>>(typeof(WorldManager), "_currentRespawnCondition").SetData(cond);
            }
        }

        public static void SetCurrent(DifficultySetting diff) {
            Log.LogInfo($"SspDifficulty SetCurrent from {diff.Id}");
            DifficultySetting newDiff = new DifficultySetting {
                Id = diff.Id,
                Hidden = true, // indicate overrided
                RespawnCondition = null // new SerializedId(diff.RespawnCondition.Id)
            };
            CopyFromConfig(ref newDiff);
            DifficultySetting.SetCurrent(newDiff);
        }

        public static void CopyFromConfig(ref DifficultySetting diff) {
            diff.Creative.Value = SspConfig.Difficulty.Creative.Value;
            diff.EatWhileHelmetClosed.Value = SspConfig.Difficulty.EatWhileHelmetClosed.Value;
            diff.DrinkWhileHelmetClosed.Value = SspConfig.Difficulty.DrinkWhileHelmetClosed.Value;
            diff.BreathingRate.Value = SspConfig.Difficulty.BreathingRate.Value;
            diff.FoodDecayRate.Value = SspConfig.Difficulty.FoodDecayRate.Value;
            diff.HungerRate.Value = SspConfig.Difficulty.HungerRate.Value;
            diff.HydrationRate.Value = SspConfig.Difficulty.HydrationRate.Value;
            diff.HygieneRate.Value = SspConfig.Difficulty.HygieneRate.Value;
            diff.JetpackRate.Value = SspConfig.Difficulty.JetpackRate.Value;
            diff.LungDamageRate.Value = SspConfig.Difficulty.LungDamageRate.Value;
            diff.MiningYield.Value = SspConfig.Difficulty.MiningYield.Value;
            diff.MoodRate.Value = SspConfig.Difficulty.MoodRate.Value;
            diff.OfflineMetabolism.Value = SspConfig.Difficulty.OfflineMetabolism.Value;
            diff.RobotBatteryRate.Value = SspConfig.Difficulty.RobotBatteryRate.Value;
            diff.WeatherLanderDamageRate.Value = SspConfig.Difficulty.WeatherLanderDamageRate.Value;
            if (diff.RespawnCondition != null) {
                diff.RespawnCondition.Id = SspConfig.Difficulty.RespawnCondition.Value;
            }
        }

        public static void SetAll(DifficultySetting diff = null, bool newDifficulty = true) {
            if (diff == null) {
                diff = DifficultySetting.Current ?? DifficultySetting.Find("Fallback");
                if (diff == null) {
                    Log.LogError("SspDifficulty SetAll Fallback not found");
                    return;
                }
            }
            if (newDifficulty) {
                SetCurrent(diff); // new DifficultySetting()
            } else {
                CopyFromConfig(ref diff);
                DifficultySetting.SetCurrent(diff);
            }
        }
    }

    namespace HarmonyPatcher {
        public class PatcherDifficulty {
            // Set initial value after startup window loaded
            [HarmonyPatch(typeof(CommandLine), "ExecutePostLaunchCommands")]
            [HarmonyPostfix]
            static void Patch_CommandLine_ExecutePostLaunchCommands() {
                Log.LogInfo("POST CommandLine.ExecutePostLaunchCommands");
                SspDifficulty.SetAll(); // not need to check Enabled
            }

            [HarmonyPatch(typeof(DifficultySetting), nameof(DifficultySetting.SetCurrent), typeof(DifficultySetting))]
            [HarmonyPrefix]
            static void Patch_DifficultySetting_SetCurrent(DifficultySetting difficultySetting) {
                Log.LogInfo("PREF DifficultySetting.SetCurrent");
                SspDifficulty.PrintBrief(difficultySetting);
            }

            // Modify difficulty when loading saved/creating new game
            [HarmonyPatch(typeof(GameManager), "StartGame")]
            [HarmonyPostfix]
            static void Patch_GameManager_StartGame() {
                Log.LogInfo("POST GameManager.StartGame");
                if (SspConfig.Difficulty.Enabled.Value) {
                    SspDifficulty.SetAll();
                }
            }

            /// Modify difficulty when joining server
            /// @ref WorldManager.DeserializeOnJoin
            [HarmonyPatch(typeof(WorldManager), nameof(WorldManager.DeserializeOnJoin))]
            [HarmonyPrefix]
            static bool Patch_WorldManager_DeserializeOnJoin(RocketBinaryReader reader) {
                if (!SspConfig.Difficulty.Enabled.Value) {
                    Log.LogInfo("PREF WorldManager.DeserializeOnJoin");
                    return true; // continue original method
                }
                Log.LogInfo("PREF WorldManager.DeserializeOnJoin (override)");
                World.CurrentId = reader.ReadString(); // Id
                int worldHash = reader.ReadInt32(); // WorldSetting
                int worldHash2 = reader.ReadInt32(); // DifficultySetting
                bool flag = reader.ReadBoolean(); // Creative
                WorldSetting.SetCurrent(worldHash);
                DifficultySetting difficulty = DifficultySetting.Find(worldHash2);
                SspDifficulty.SetAll(difficulty); // override difficulty params
                if (difficulty.Creative) {
                    WorldManager.Instance.GameMode = GameMode.Creative;
                    // WorldManager.SetCreativeMode(true);
                }
                // TerrainGeneration.InitializeNoise(WorldManager.Seed);
                WorldManager.Instance.InitializeWorldEnvironment();
                return false; // skip original method
            }

            /// <param name="__instance">this</param>
            [HarmonyPatch(typeof(Human), "TakeBreath")]
            [HarmonyPrefix]
            static bool Patch_Human_TakeBreath(Human __instance) {
                // Log.LogInfo("PREF Human.TakeBreath (override)");
                if (__instance.OrganLungs != null && __instance.OrganLungs.Indestructable != SspConfig.GamePlay.HumanGodMode.Value) {
                    Log.LogInfo($"Human.TakeBreath ToggleGodMode for {__instance.DisplayName}");
                    __instance.ToggleGodMode(); // handle GodMode, clients cannot change server
                }
                if (DifficultySetting.Current.BreathingRate.IsZero()) {
                    __instance.Oxygenation += 0.0048f;
                    AccessTools.PropertySetter(typeof(Human), "OxygenQuality").Invoke(__instance, new object[] { 2f }); // protected setter, >= 1.5 is good
                    return false; // skip original method
                }
                return true; // continue original method
            }

        }
    }

    namespace Dumper {
        public sealed class DifficultyDumper : DumperBase<DifficultyDumper>, IDumper {
            public override string GetString() {
                // string buf = "RespawnConditions = [" + String.Join(", ", WorldManager.RespawnConditions) + ']';
                string buf = "RespawnConditions = [Normal]";
                foreach (DifficultySetting d in DifficultySetting.AllSettings) {
                    buf += '\n';
                    SspDifficulty.Print(d, (string str) => { buf += '\n' + str; });
                }
                return buf;
            }
        }
    }
}